import {RECORDER_INTERFACE, RECORDER_TYPE} from "./recorder_interface";

const MediaRecorderTxt = "MediaRecorder";
const sampleRateTxt = "sampleRate";

class WebMRecorder implements RECORDER_INTERFACE {
    private receiverCb?: (data: Float32Array) => void;
    private errorCb?: (e: any) => void;
    private recorder?: any;
    private isConnected: boolean;
    private readingInfo: any;
    private isStarted: boolean;
    private sampleRate: number;

    constructor(private mediaStream: any, sampleRate: number) {
        this.isConnected = false;
        this.sampleRate = sampleRate;
        this.readingInfo = {sampleRate: sampleRate};
        this.isStarted = false;
    }

    getSampleRate(): number {
        return this.sampleRate;
    }

    getRecorderType(): number {
        return RECORDER_TYPE.WEBM_RECORDER;
    }

    isSupport(): boolean {
        let MR = window[MediaRecorderTxt];
        let onData = "ondataavailable";
        let webmType = "audio/webm; codecs=pcm";
        return MR && (onData in MR.prototype) && MR.isTypeSupported(webmType);;
    }

    onError(fn: (e: any) => void): void {
        this.errorCb = fn;
    }

    connect(): Promise<boolean> {
        console.log("webm connecting....");
        let MR = window[MediaRecorderTxt];
        let webmType = "audio/webm; codecs=pcm";
        let onData = "ondataavailable";
        this.recorder = new MR(this.mediaStream, {mimeType: webmType});
        this.isConnected = true;
        this.recorder[onData] = this.onDataAvailable.bind(this);
        return Promise.resolve(true);
    }

    private onDataAvailable(e: any) {
        let reader = new FileReader();
        reader.onloadend = () => {
            if (this.isConnected) {
                let f32Data = this.webMExtract(new Uint8Array(reader.result as Uint8Array), this.readingInfo);
                if (!f32Data) {
                    return;
                }

                if (f32Data == -1) {
                    console.log("!!!!!!!!!!!!!! webM recorder got data error !!!!!!!!!!!!!");
                    this.disconnect();
                    if (this.errorCb) {
                        this.errorCb("无法提取，降级");
                    }
                    return;
                }

                if (this.receiverCb) {
                    this.receiverCb(f32Data);
                }
            }else {
                console.log("webM 多余的回调")
            }
        }

        reader.readAsArrayBuffer(e.data);
    }

    async disconnect(): Promise<boolean> {
        console.log("webm disconnecting........");
        this.isConnected = false;
        if (this.isStarted) {
            await this.stop();
        }
        this.recorder = undefined;

        return true;
    }

    onDataReceive(fn: (data: any) => void): void {
        this.receiverCb = fn;
    }

    start(): Promise<boolean> {
        console.log("start", this.recorder, this.isStarted)
        if (this.recorder && !this.isStarted) {
            this.recorder.start(20);
            this.isStarted = true;
            return Promise.resolve(true);
        }

        return Promise.reject("录音器已销户");
    }

    stop(): Promise<boolean> {
        console.log("stop", this.recorder, this.isStarted)
        if (this.recorder && this.isStarted) {
            this.recorder.stop();
            this.isStarted = false;
            return Promise.resolve(true);
        }

        return Promise.reject("录音器已销户");
    }

    private bytesEqual(bytes1: Uint8Array|number[]|undefined, bytes2: Uint8Array|number[]): boolean {
        if (!bytes1 || bytes1.length != bytes2.length) {
            return false;
        }

        if (bytes1.length == 1) {
            return bytes1[0] == bytes2[0];
        }

        for (let i = 0; i < bytes1.length; i++) {
            if (bytes1[i] != bytes2[i]) {
                return false;
            }
        }

        return true;
    }

    private bytesInt(bytes: Uint8Array|number[]): number {
        let s = "";
        for (let i = 0; i < bytes.length; i++) {
            let n: number = bytes[i];
            s += ((n < 16 ? "0": "") + n.toString(16));
        }

        return parseInt(s, 16) || 0;
    }

    //读取一个可变长数值字节数组
    private readMatroskaVInt(arr: Uint8Array|number[], pos: number[], trim: boolean = false): number[]|undefined {
        let i = pos[0];
        if (i >= arr.length) {
            return undefined;
        }
        let b0 = arr[i], b2 = ("0000000"+b0.toString(2)).substr(-8);
        let m = /^(0*1)(\d*)$/.exec(b2);
        if (!m) {
            return undefined;
        }

        let len = m[1].length, val: number[] = [];
        if (i + len > arr.length) {
            return undefined;
        }
        for(let i2 = 0; i2 < len; i2++) {
            val[i2] = arr[i];
            i++;
        }

        if (trim) {
            val[0] = parseInt(m[2]||'0',2);
        }

        pos[0] = i;

        return val;
    }

    //读取一个自带长度的内容字节数组
    private readMatroskaBlock(arr: Uint8Array|number[], pos: number[]): number[]|undefined {
        let lenVal = this.readMatroskaVInt(arr, pos, true);
        if (!lenVal) {
            return undefined;
        }
        let len = this.bytesInt(lenVal);
        let i = pos[0], val: number[] = [];
        if (len < 0x7FFFFFFF){ //超大值代表没有长度
            if (i + len > arr.length) {
                return undefined;
            }

            for(let i2 = 0; i2 < len; i2++){
                val[i2] = arr[i];
                i++;
            }
        }
        pos[0] = i;

        return val;
    }

    private webMExtract(inBytes: Uint8Array, scope: any): Float32Array|undefined|-1 {
        if (!scope.pos) {
            scope.pos = [0];
            scope.tracks = {};
            scope.bytes = [];
        }

        let tracks = scope.tracks, position = [scope.pos[0]];
        let endPos = () => {
            scope.pos[0] = position[0]
        };

        let sBL = scope.bytes.length;
        let bytes = new Uint8Array(sBL + inBytes.length);
        bytes.set(scope.bytes);
        bytes.set(inBytes,sBL);
        scope.bytes = bytes;

        //先读取文件头和Track信息
        if(!scope._ht){
            this.readMatroskaVInt(bytes, position);//EBML Header
            this.readMatroskaBlock(bytes, position);//跳过EBML Header内容
            if (!this.bytesEqual(this.readMatroskaVInt(bytes, position), [0x18,0x53,0x80,0x67])) {
                return undefined;//未识别到Segment
            }
            this.readMatroskaVInt(bytes, position);//跳过Segment长度值
            while (position[0] < bytes.length){
                let eid0 = this.readMatroskaVInt(bytes, position);
                let bytes0 = this.readMatroskaBlock(bytes, position);
                let pos0=[0], audioIdx=0;
                if(!bytes0) {
                    console.log("数据不全，等待缓存");
                    return undefined;
                }//数据不全，等待缓冲

                //Track完整数据，循环读取TrackEntry
                if(this.bytesEqual(eid0, [0x16,0x54,0xAE,0x6B])) {
                    while (pos0[0] < bytes0.length){
                        let eid1 = this.readMatroskaVInt(bytes0, pos0);
                        let bytes1 = this.readMatroskaBlock(bytes0, pos0);
                        let pos1=[0], track:any = {channels:0,sampleRate:0};
                        if (this.bytesEqual(eid1, [0xAE])) {//TrackEntry
                            while (pos1[0] < bytes1!.length){
                                let eid2 = this.readMatroskaVInt(bytes1!, pos1);
                                let bytes2 = this.readMatroskaBlock(bytes1!, pos1);
                                let pos2=[0];
                                if (this.bytesEqual(eid2, [0xD7])) {//Track Number
                                    let val = this.bytesInt(bytes2!);
                                    track.number = val;
                                    tracks[val] = track;
                                }else if (this.bytesEqual(eid2, [0x83])) {//Track Type
                                    let val = this.bytesInt(bytes2!);
                                    if (val == 1) {
                                        track.type = "video";
                                    }

                                    else if(val==2) {
                                        track.type = "audio";
                                        if (!audioIdx) {
                                            scope.track0=track;
                                        }
                                        track.idx = audioIdx++;
                                    }else {
                                        track.type="Type-"+val;
                                    }
                                }else if(this.bytesEqual(eid2, [0x86])) {//Track Codec
                                    let str="";
                                    for (let i = 0; i < bytes2!.length; i++) {
                                        str += String.fromCharCode(bytes2![i]);
                                    }
                                    track.codec = str;
                                }else if(this.bytesEqual(eid2, [0xE1])) {
                                    while (pos2[0] < bytes2!.length) {//循环读取 Audio 属性
                                        let eid3 = this.readMatroskaVInt(bytes2!, pos2);
                                        let bytes3 = this.readMatroskaBlock(bytes2!, pos2);
                                        //采样率、位数、声道数
                                        if (this.bytesEqual(eid3, [0xB5])) {
                                            let val = 0, arr = new Uint8Array(bytes3!.reverse()).buffer;
                                            if (bytes3!.length == 4) {
                                                val=new Float32Array(arr)[0];
                                            }else if (bytes3!.length == 8) {
                                                val=new Float64Array(arr)[0];
                                            }else {
                                                console.log("WebM Track !Float");
                                            }
                                            track[sampleRateTxt] = Math.round(val);
                                        }else if (this.bytesEqual(eid3, [0x62,0x64])) {
                                            track.bitDepth = this.bytesInt(bytes3!);
                                        }else if(this.bytesEqual(eid3, [0x9F])) {
                                            track.channels = this.bytesInt(bytes3!);
                                        }
                                    }
                                }
                            }
                        }
                    }

                    scope._ht = 1;
                    endPos();
                    break;
                }
            }
        }

        //校验音频参数信息，如果不符合代码要求，统统拒绝处理
        let track0 = scope.track0;
        if (!track0) {
            console.log("track0不存在", track0);
            return undefined;
        }
        if (track0.bitDepth == 16 && /FLOAT/i.test(track0.codec)) {
            track0.bitDepth = 32; //chrome v66 实际为浮点数
            console.log("WebM 16改32位");
        }

        //console.log(track0, scope)
        if (track0[sampleRateTxt] != scope[sampleRateTxt] || track0.bitDepth!=32 || track0.channels<1 || !/(\b|_)PCM\b/i.test(track0.codec)){
            scope.bytes = [];//格式非预期 无法处理，清空缓冲数据
            if(!scope.bad) {
                console.log("WebM Track非预期");
            }
            scope.bad = 1;
            return -1;
        }

        //循环读取Cluster内的SimpleBlock
        let datas: Uint8Array[] = [], dataLen = 0;
        while (position[0] < bytes.length) {
            let eid1 = this.readMatroskaVInt(bytes, position);
            let bytes1 = this.readMatroskaBlock(bytes, position);
            if (!bytes1) {
                break;
            }//数据不全，等待缓冲
            if (this.bytesEqual(eid1, [0xA3])) {//SimpleBlock完整数据
                let trackNo = bytes1[0]&0xf;
                let track = tracks[trackNo];
                if (track && track.idx === 0) {
                    let u8arr = new Uint8Array(bytes1.length-4);
                    for(let i = 4; i < bytes1.length; i++) {
                        u8arr[i-4] = bytes1[i];
                    }

                    datas.push(u8arr);
                    dataLen += u8arr.length;
                }
            }
            endPos();
        }

        if(dataLen) {
            let more = new Uint8Array(bytes.length - scope.pos[0]);
            more.set(bytes.subarray(scope.pos[0]));
            scope.bytes = more; //清理已读取了的缓冲数据
            scope.pos[0] = 0;

            let u8arr = new Uint8Array(dataLen); //已获取的音频数据
            for (let i = 0, i2 = 0; i < datas.length; i++) {
                u8arr.set(datas[i],i2);
                i2 += datas[i].length;
            }

            let arr = new Float32Array(u8arr.buffer);

            if (track0.channels > 1) {//多声道，提取一个声道
                let arr2: number[] = [];
                for(let i = 0; i < arr.length; ) {
                    arr2.push(arr[i]);
                    i += track0.channels;
                }

                arr = new Float32Array(arr2);
            }

            return arr;
        }

        return undefined;
    };
}

export {WebMRecorder};
